package dhz.storage.controllers;

import dhz.storage.util.FileUtil;
import dhz.storage.util.Response;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.UserPrincipalNotFoundException;
import java.security.Security;
import java.util.*;
import java.util.zip.ZipException;

@RestController
@RequestMapping("/fs")
@Api(tags = "文件系统的接口，包括复制、移动、文件列表、删除")
public class FileSysControllers {
    static final Logger logger = LoggerFactory.getLogger(FileSysControllers.class);
    /**
     * 根据给出的path，获得这个path下全部的文件的信息，包括文件名、文件大小、普通文件/文件夹、最后一次修改
     * @param path 路径，末尾不是/
     * @return 1001是查询成功，500是失败，501是没有这个文件夹，数据是列表
     * size 代表大小，单位是byte，长整形
     * name 是文件名，字符串
     * last_modified 是最后一次编辑，字符串
     * type 是文件类型，"d"是路径，"f"是普通文件
     * hidden 表示这个文件是不是隐藏文件，boolean
     * */
    @PostMapping("/list")
    @ApiOperation(value = "获取一个路径下的文件列表", notes = "获取一个文件列表，具体的信息包括文件名，文件大小（单位是比特），是否是隐藏文件，是否是文件夹，最后一次修改")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "查询成功"),
            @ApiResponse(code = 501, message = "文件夹不存在"),
            @ApiResponse(code = 500, message = "其他错误")
    })
    public Response ls(@RequestParam("path") String path){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        // 待查询的文件夹的绝对路径
        String base_dir = "/mnt/students/" + identification + (path.compareTo("/")==0?"":path);
        File target_dir = new File(base_dir);
        List<Map<String, Object>> data = new LinkedList<>();
        Response res = new Response(data);
        // 没有这个文件夹的情况
        if(!target_dir.exists() || !target_dir.isDirectory()){
            res.setCode(501);
            res.setMessage("被查询的文件夹不存在");
            logger.error("Error occurred when "+identification+" tries to list files in "+base_dir);
            return res;
        }
        // 获取全部的文件
        File[] files = target_dir.listFiles();
        for(File f:files){
            HashMap<String, Object> file_data = new HashMap<>();
            // 把文件大小放进去
            file_data.put("size", f.length());
            // 把文件名字放进去
            file_data.put("name", f.getName());
            // 把文件最后一次编辑放进去
            file_data.put("last_modified", new Date(f.lastModified()).toString());
            // 文件类型
            file_data.put("type", f.isDirectory()?"d":"f");
            // 文件是不是隐藏文件
            file_data.put("hidden", f.isHidden());
            data.add(file_data);
        }
        res.setCode(1001);
        res.setMessage("查询成功");
        return res;
    }

    /**
     * 删除一个文件或者文件夹
     * @param path 被删除的文件或者文件夹的路径
     * @return 一个Response对象，code是1001代表删除成功，code是500意味着没有这个文件，code是501意味着IO错误
     * */
    @PostMapping("/delete")
    @ApiOperation(value = "删除文件或者文件夹", notes = "前端通过给后端一个路径，后端将在用户文件夹中删除这个文件或者文件夹，后端会自动判断这是不是文件夹")
    @ApiResponses({
            @ApiResponse(code = 500, message = "发生IO错误"),
            @ApiResponse(code = 501, message = "没有这个文件/文件夹"),
            @ApiResponse(code = 1001, message = "删除成功")
    })
    public Response deleteFile(@RequestParam("path")String path){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String folder_path = "/mnt/students/" + identification + path;
        Response res = new Response(null);
        res.setCode(200);
        res.setMessage("删除成功");
        Path target = Paths.get(folder_path);
        try{
            FileUtil.delete(target);
            logger.info(identification+" successfully deleted file/folder "+folder_path);
        }catch(NoSuchFileException e){
            res.setCode(501);
            res.setMessage("没有这个文件/文件夹");
            logger.error("Error occurred when "+identification+" tries to delete file/folder "+folder_path+" "+e.toString());
        }catch(IOException e){
            res.setCode(500);
            res.setMessage("删除过程中出现IO错误，请待会再试");
            logger.error("Error occurred when "+identification+" tries to delete file/folder "+folder_path+" "+e.toString());
        }
        return res;
    }

    /**
     * 将一个文件/文件夹从一个路径下复制到另一个路径下
     * @param src_path 待复制的文件/文件夹的路径
     * @param dst_path 前往的路径
     * @return 一个Response对象，1001是复制成功，500是IO错误，501是父路径复制到子路径，502是前往的路径不存在
     * */
    @PostMapping("/copy")
    @ApiOperation(value = "把一个文件/文件夹复制到另一个路径下", notes = "把一个文件夹/文件从一个路径下复制到另一个路径下，注意：不能把父路径复制到子路径，文件还是文件夹后端会自动判断")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "拷贝成功"),
            @ApiResponse(code = 500, message = "其他错误"),
            @ApiResponse(code = 501, message = "尝试把父路径拷贝到子路径"),
            @ApiResponse(code = 502, message = "目标路径不是文件夹")
    })
    public Response copyFile(@RequestParam("source")String src_path, @RequestParam("destination")String dst_path){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        src_path = "/mnt/students/"+identification + src_path;
        File target_file = new File(src_path);// 待移动的文件夹/文件
        Response res = new Response(null);
        String base_dst_path = "/mnt/students/"+identification + dst_path;
        File dst_dir = new File(base_dst_path);
        if(!dst_dir.exists() || !dst_dir.isDirectory()){
            res.setCode(502);
            res.setMessage("目标路径不存在/不是路径");
            logger.warn(identification+" tries to copy file to "+base_dst_path+" but latter path don't exist");
            return res;
        }
        if(target_file.isDirectory() && base_dst_path.startsWith(src_path)){
            res.setCode(501);
            res.setMessage("不能将父路径拷贝到子路径");
            logger.warn(identification+" tries to copy "+src_path + " to "+base_dst_path+" but was prevented because former one is parent directory of latter one");
            return res;
        }
        try{
            String new_file_name = FileUtil.getFileName(base_dst_path + "/" + target_file.getName());
            Path src = Paths.get(src_path);
            Path dst = Paths.get(new_file_name);
            FileUtil.copy(src, dst);
            logger.info(identification+" successfully copied "+ src.toString()+" to "+dst.toString());
        }catch(IOException e){
            res.setCode(500);
            res.setMessage("复制失败");
            e.printStackTrace();
            return res;
        }
        res.setCode(1001);
        res.setMessage("复制成功");
        return res;
    }

    /**
     * 在任何一个地方创建一个新的文件夹，不能和同目录下的其他文件/文件夹重名
     * @param path 新建文件夹的位置
     * @param name 新建文件夹的名字
     * @return 一个Response对象，1001是创建成功，501是同名，500是其他错误
     * */
    @PostMapping("/mkdir")
    @ApiOperation(value = "创建文件夹", notes = "在任何一个地方创建一个文件夹，不能和同目录下的文件/文件夹同名")
    @ApiResponses({
            @ApiResponse(code = 1001, message = "创建成功"),
            @ApiResponse(code = 403, message = "用户不存在"),
            @ApiResponse(code = 501, message = "存在同名文件夹"),
            @ApiResponse(code = 500, message = "创建失败")
    })
    public Response makeDirectory(@RequestParam("path")String path, @RequestParam("name")String name){
        Response res = new Response(null);
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String folder_path = "/mnt/students/"+identification+path+"/"+name;
        Path new_folder = Paths.get(folder_path);
        res.setCode(1001);
        res.setMessage("创建成功");
        try{
            FileUtil.mkdir(new_folder);
            logger.info(identification+" successfully make a directory in "+new_folder.toString());
        }catch(UserPrincipalNotFoundException e){
            res.setCode(403);
            res.setMessage("用户不存在");
            logger.error("Failed to make directory because "+identification+" don't exist on file system");
        }catch(FileAlreadyExistsException e){
            res.setCode(501);
            res.setMessage("文件夹已经存在");
            logger.error("Failed to make directory because "+new_folder.toString()+" already exists");
        }catch(IOException e){
            res.setCode(500);
            res.setMessage("IO错误，请待会再试");
            logger.error("Failed to make directory in "+new_folder.toString()+" due to IOException");
        }
        return res;
    }

    /**
     * 重命名一个文件/文件夹，重新命名的文件/文件夹还在原位
     * */
    @PostMapping("/rename")
    @ApiOperation(value = "重命名文件/文件夹", notes = "重命名一个文件或者文件夹，注意，不能和同路径下的其他文件重名")
    @ApiResponses({
            @ApiResponse(code = 500, message = "存在同名文件"),
            @ApiResponse(code = 501, message = "被重命名的文件不存在"),
            @ApiResponse(code = 502, message = "IO出现错误"),
            @ApiResponse(code = 1001, message = "重命名成功"),
            @ApiResponse(code = 1002, message = "新旧名称一样")
    })
    public Response rename(@RequestParam("name")String name, @RequestParam("target")String target){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String base = "/mnt/students/"+identification;
        Response res = new Response(null);
        res.setCode(1001);
        res.setMessage("重命名成功");
        if(name.compareTo(new File(base+target).getName()) == 0){
            res.setCode(1002);
            res.setMessage("与旧名字相同");
            return res;
        }
        try{
            FileUtil.rename(Paths.get(base + target), name);
        }catch(FileAlreadyExistsException e){
            e.printStackTrace();
            res.setCode(500);
            res.setMessage("同名文件已存在");
        }catch(NoSuchFileException e){
            e.printStackTrace();
            res.setCode(501);
            res.setMessage("被重命名文件不存在");
        }catch(IOException e){
            e.printStackTrace();
            res.setCode(502);
            res.setMessage("发生IO错误");
        }
        return res;
    }
    @PostMapping("/move")
    @ApiOperation(value = "将一个文件/文件夹从一个地方移动到另一个地方", notes = "移动一个文件/文件夹，移动到新目录之后，如果出现重名，会自动在后面加一个(n)")
    @ApiResponses({
            @ApiResponse(code = 200, message = "文件移动成功"),
            @ApiResponse(code = 201, message = "文件不需要移动，因为在同一路径下"),
            @ApiResponse(code = 500, message = "被移动的文件/文件夹不存在"),
            @ApiResponse(code = 501, message = "IO过程出现错误")
    })
    public Response move(@RequestParam("source")String src, @RequestParam("targetDir")String target_dir){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String base = "/mnt/students/"+identification;
        File src_file = new File(base+src);
        Response res = new Response(null);
        res.setCode(1001);
        res.setMessage("文件移动成功");
        String new_name = FileUtil.getFileName(base + target_dir + "/" + src_file.getName());
        try{
            if(Paths.get(base+src).getParent().toString().compareTo(Paths.get(new_name).getParent().toString()) == 0){
                logger.warn("move operation prevented because "+new_name+" and "+base+src+" are at same directory");
                res.setCode(1002);
                res.setMessage("位置没有变化");
                return res;
            }
            FileUtil.mv(Paths.get(base+src), Paths.get(new_name));
            logger.info(identification+" successfully move "+base+src+" to "+new_name);
        }catch(NoSuchFileException e){
            e.printStackTrace();
            res.setCode(500);
            res.setMessage("被移动的文件不存在");
            logger.error(identification+" tries to move file "+base+src+" which doesn't exist");
        }catch(IOException e){
            e.printStackTrace();
            res.setCode(501);
            res.setMessage("IO过程中出现异常");
            logger.error(identification+" failed to move "+base+src+" to "+new_name+" due to IOException");
        }
        return res;
    }

    /**
     * 提供在线解压缩功能，这样用户如果想要上传一个文件夹，就不用在本地一个个上传了，而是可以打包好再上传
     * */
    @PostMapping("/unzip")
    @ApiOperation(value = "在线解压zip文件", notes = "在线解压zip文件，这样一来如果用户想要将一整个文件夹发送上去，就可以本地打包好再发送而不是一个个发送")
    public static Response unzip(@RequestParam("src") String src, @RequestParam("dst") String dst){
        String identification = SecurityUtils.getSubject().getPrincipal().toString();
        String base = "/mnt/students/"+identification;
        Response res = new Response(null);
        try{
            Path target = Paths.get(base+dst);
            FileUtil.unzip(Paths.get(base+src), target);
            logger.info(identification+" successfully unzipped "+target.toString());
        }catch(NoSuchFileException e){
            e.printStackTrace();
            res.setCode(500);
            res.setMessage("被解压缩文件或者目标路径不存在");
            logger.info(identification+" tries to unzip file "+base+src+" which doesn't exists");
            return res;
        }catch(ZipException e){
            e.printStackTrace();
            res.setCode(501);
            res.setMessage("zip文件格式有问题");
            logger.info(identification+" failed to unzip the provided file "+base+src+" due to zip format problem");
            return res;
        }catch(IOException e){
            e.printStackTrace();
            res.setCode(502);
            res.setMessage("文件IO过程中出现错误");
            logger.info(identification+" failed to unzip the provided file "+base+src+" due to IOException");
            return res;
        }
        res.setCode(1001);
        res.setMessage("解压缩成功");
        return res;
    }
}
